﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using NHibernate;
using NHibernate.Linq;
using NHibernate.Criterion;
using Bizbase.Repository.Nhibernate;

namespace Bizbase.Repository
{
    public class DataRepository<TEntity> : IRepository<TEntity> where TEntity : class
    {
        private ISessionFactory _sessionFactory;
        private ISession _session;

        public DataRepository(IDatabase database)
        {
            _sessionFactory = database.SessionFactory;
            _session = _sessionFactory.OpenSession();
        }

        #region save, delete

        public TEntity Save(TEntity entity)
        {
            return TryCatch(() =>
              {
                  ISession session = _session;
                  session.Save(entity);
                  session.Flush();

                  return entity;
              });
        }

        public void Update(TEntity entity)
        {
            TryCatch(() =>
           {
               ISession session = _session;
               session.Update(entity);
               session.Flush();

               return null;
           });
        }

        public TEntity SaveOrUpdate(TEntity entity)
        {
            return TryCatch(() =>
               {
                   ISession session = _session;
                   session.SaveOrUpdate(entity);
                   session.Flush();

                   return entity;
               });
        }

        public void Delete(TEntity entity)
        {
            TryCatch(() =>
            {
                ISession session = _session;
                session.Delete(entity);
                session.Flush();

                return null;
            });
        }

        public void DeleteAll()
        {
            TryCatch(() =>
            {
                ISession session = _session;
                session.Delete(String.Format("from {0} ", typeof(TEntity)));

                return null;
            });
        }
        #endregion

        #region query: Get / FindAll
        public TEntity Get(object id)
        {
            ISession session = _session;
            return session.Get<TEntity>(id);
        }

        public IList<TEntity> FindAll()
        {
            ISession session = _session;
            ICriteria criteria = session.CreateCriteria(typeof(TEntity));
            return criteria.List<TEntity>();
        }

        public IList<TEntity> Find(string propertyName, object value)
        {
            if (value == null)
                return Find(Expression.IsNull(propertyName));

            if (value is string)
                return Find(Expression.Like(propertyName, value));

            return Find(Expression.Eq(propertyName, value));
        }

        public IList<TEntity> Find(params ICriterion[] criteria)
        {
            ISession session = _session;
            ICriteria crit = RepositoryHelper<TEntity>.CreateCriteriaFromArray(session, criteria);
            return crit.List<TEntity>();
        }

        public IList<TEntity> Find(Order[] orders, params ICriterion[] criteria)
        {
            ISession session = _session;
            ICriteria crit = RepositoryHelper<TEntity>.CreateCriteriaFromArray(session, criteria);
            foreach (Order order in orders) { crit.AddOrder(order); }
            return crit.List<TEntity>();
        }

        public IList<TEntity> Find(int firstResult, int numberOfResults, Order[] selectionOrder,
            params ICriterion[] criteria)
        {
            ISession session = _session;
            ICriteria crit = RepositoryHelper<TEntity>.CreateCriteriaFromArray(session, criteria);
            crit.SetFirstResult(firstResult).SetMaxResults(numberOfResults);

            foreach (Order order in selectionOrder)
            {
                crit.AddOrder(order);
            }

            return crit.List<TEntity>();
        }
        #endregion


        private TEntity TryCatch(Func<TEntity> action)
        {
            try
            {
                return action();
            }
            catch (Exception ex)
            {
                throw new HibernateException(ex);
            }
        }

        public ITransaction BeginTransaction()
        {
            ISession session = _session;
            return new DataTransaction(session);
        }

        public void Dispose()
        {
            if (_session != null)
            {
                _session.Flush();
                _session.Close();
                _session.Dispose();
                _session = null;
            }
        }
    }

    internal class RepositoryHelper<T>
    {
        public static ICriteria CreateCriteriaFromArray(ISession session, ICriterion[] criteria)
        {
            ICriteria crit = session.CreateCriteria(typeof(T));

            foreach (ICriterion criterion in criteria)
            {
                if (criterion == null)
                    continue;

                crit.Add(criterion);
            }

            return crit;
        }
    }
}
